﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CreditAgricole.GenerateData.Tools
{
    public sealed class ConsoleColors
    {
        private ConsoleColor foregroundColor, backgroundColor;
        public ConsoleColors(ConsoleColor foregroundColor, ConsoleColor backgroundColor)
        {
            this.foregroundColor = foregroundColor;
            this.backgroundColor = backgroundColor;
        }

        public ConsoleColor ForegroundColor
        {
            get { return foregroundColor; }
        }

        public ConsoleColor BackgroundColor
        {
            get { return backgroundColor; }
        }

        public void ApplyToConsole()
        {
            Console.ForegroundColor = foregroundColor;
            Console.BackgroundColor = backgroundColor;
        }

        public static ConsoleColors GetCurrent()
        {
            return new ConsoleColors(Console.ForegroundColor, Console.BackgroundColor);
        }

        public static ConsoleColors GetConsoleDefault()
        {
            // store current console colors
            ConsoleColors initial = ConsoleColors.GetCurrent();
            // reset console colors to default value
            Console.ResetColor();
            // get current (now default) colors
            ConsoleColors consoledefault = ConsoleColors.GetCurrent();
            // restore initial colors
            initial.ApplyToConsole();
            // return default
            return consoledefault;
        }
    }

    public sealed class CursorPosition
    {        
        private int left, top;
        public CursorPosition(int left, int top)
        {
            if (left < 0 || top < 0) throw new ArgumentOutOfRangeException("Coordinates must be positive or zero.");
            this.left = left;
            this.top = top;
        }

        public int Left
        {
            get { return left; }
        }

        public int Top
        {
            get { return top; }
        }

        public void ApplyToConsole()
        {
            Console.SetCursorPosition(left, top);
        }

        public static CursorPosition GetCurrent()
        {
            return new CursorPosition(Console.CursorLeft, Console.CursorTop);
        }
    }

    public enum ConsoleBoxBorderStyle
    {
        Single,
        Double
    }

    public class ConsoleBox
    {
        #region Fields
        ConsoleBoxBorderStyle borderStyle;
        ConsoleColor foregroundColor, backgroundColor;
        int width, height;
        int left, top;
        string title;
        string[,] contentBuffer; // Box content, without borders
        string[,] boxBuffer; // Whole box buffer
        #endregion

        #region Ctor
        public ConsoleBox(int height, int width) : this(height, width, ConsoleColors.GetConsoleDefault().ForegroundColor, ConsoleColors.GetConsoleDefault().BackgroundColor, string.Empty, ConsoleBoxBorderStyle.Single) { }
        public ConsoleBox(int height, int width, ConsoleBoxBorderStyle borderStyle) : this(height, width, ConsoleColors.GetConsoleDefault().ForegroundColor, ConsoleColors.GetConsoleDefault().BackgroundColor, string.Empty, borderStyle) { }
        public ConsoleBox(int height, int width, string title) : this(height, width, ConsoleColors.GetConsoleDefault().ForegroundColor, ConsoleColors.GetConsoleDefault().BackgroundColor, title, ConsoleBoxBorderStyle.Single) { }
        public ConsoleBox(int height, int width, string title, ConsoleBoxBorderStyle borderStyle) : this(height, width, ConsoleColors.GetConsoleDefault().ForegroundColor, ConsoleColors.GetConsoleDefault().BackgroundColor, title, borderStyle) { }
        public ConsoleBox(int height, int width, ConsoleColor foregroundColor, ConsoleColor backgroundColor) : this(height, width, foregroundColor, backgroundColor, string.Empty, ConsoleBoxBorderStyle.Single) { }
        public ConsoleBox(int height, int width, ConsoleColor foregroundColor, ConsoleColor backgroundColor, ConsoleBoxBorderStyle borderStyle) : this(height, width, foregroundColor, backgroundColor, string.Empty, borderStyle) { }
        public ConsoleBox(int height, int width, ConsoleColor foregroundColor, ConsoleColor backgroundColor, string title) : this(height, width, foregroundColor, backgroundColor, title, ConsoleBoxBorderStyle.Single) { }
        public ConsoleBox(int height, int width, ConsoleColor foregroundColor, ConsoleColor backgroundColor, string title, ConsoleBoxBorderStyle borderStyle)
        {
            this.foregroundColor = foregroundColor;
            this.backgroundColor = backgroundColor;
            this.width = width;
            this.height = height;
            this.title = title;
            this.left = -1;
            this.top = -1;
            this.borderStyle = borderStyle;

            // Fill content buffer with spaces
            contentBuffer = new string[height - 2, width - 2];
            for (int i = 0; i < height - 2; i++)
            {
                for (int j = 0; j < width - 2; j++)
                {
                    contentBuffer[i, j] = " ";
                }
            }

            GenerateBoxBuffer();
        }
        #endregion

        #region Properties
        public int Width
        {
            get { return width; }
        }

        public int Height
        {
            get { return height; }
        }

        public int Left
        {
            get { return left; }
        }

        public int Top
        {
            get { return top; }
        }

        public string[,] ContentBuffer
        {
            get { return contentBuffer; }
        }

        public string[,] BoxBuffer
        {
            get { return boxBuffer; }
        }

        public ConsoleColors CurrentColors
        {
            get { return new ConsoleColors(foregroundColor, backgroundColor); }
        }

        public string Title
        {
            get { return title; }
            set
            {
                if (title != value)
                {
                    title = value;
                    GenerateBoxBuffer();
                    Refresh();
                }
            }
        }

        public ConsoleBoxBorderStyle BorderStyle
        {
            get { return BorderStyle; }
            set
            {
                if (borderStyle != value)
                {
                    borderStyle = value;
                    GenerateBoxBuffer();
                    Refresh(); // Redraw box with new border style
                }
            }
        }

        public ConsoleColor ForegroundColor
        {
            get { return foregroundColor; }
            set
            {
                if (foregroundColor != value)
                {
                    foregroundColor = value;
                    Refresh();
                }
            }
        }

        public ConsoleColor BackgroundColor
        {
            get { return backgroundColor; }
            set
            {
                if (backgroundColor != value)
                {
                    backgroundColor = value;
                    Refresh();
                }
            }
        }
        #endregion

        #region Methods
        public void Clear() 
        {
            // Store current cursor position and console colors
            CursorPosition cp = CursorPosition.GetCurrent();
            ConsoleColors cc = ConsoleColors.GetCurrent();

            // Apply default
            Console.ResetColor();

            // if box coordinates greater than console size, nothing to clear
            // Remove box from console, using default console foreground/background colors
            for (int i = 0; i < height; i++)
            {
                if ((top + i) < Console.BufferHeight && (left < Console.BufferWidth))
                {
                    Console.SetCursorPosition(left, top + i);
                    string s = string.Empty;
                    for (int j = 0; j < width; j++)
                    {
                        if ((left + j) < Console.BufferWidth) s += " ";
                    }
                    if (!string.IsNullOrEmpty(s))
                    {
                        // if last line in buffer and overflow, increase buffer size before writing 
                        // to avoid generation of a new line in buffer, then reposition the cursor to top left corner
                        // and set buffer size back to original size
                        if ((top + i) == Console.BufferHeight - 1)
                        {
                            Console.SetBufferSize(Console.BufferWidth, Console.BufferHeight + 1);
                            Console.Write(s);
                            Console.SetCursorPosition(0, 0);
                            Console.SetBufferSize(Console.BufferWidth, Console.BufferHeight - 1);                           
                        }
                        else
                            Console.Write(s);
                    }
                }
            }

            // Reset cursor at initial position and initial colors
            cp.ApplyToConsole();
            cc.ApplyToConsole();
        }

        public void DrawAt(int left, int top) 
        {
            if (left < 0 || top < 0) throw new ArgumentOutOfRangeException("Coordinates must be positive or zero");
            if (this.left >= 0 || this.top >= 0) Clear(); // Clear current box
            
            // Set new coordinates
            this.left = left;
            this.top = top;
            
            // No need to regenerate box buffer here

            // Redraw box
            Refresh(); 
        }

        public void ResizeTo(int height, int width) 
        {
            // Check values
            if (height < 3 || width < 3) throw new ArgumentOutOfRangeException("Height and width must at least be 3.");

            // Clear current box
            Clear();

            // store previous height and width
            int h = this.height;
            int w = this.width;

            // set new values
            this.height = height;
            this.width = width;

            // create new content buffer with new size
            string[,] tmp = new string[height - 2, width - 2];

            // copy previous content to new buffer, with truncation if needed
            for (int i = 0; i < height - 2; i++)
            {
                for (int j = 0; j < width - 2; j++)
                {
                    if (i < h - 2)
                    {
                        tmp[i, j] = (j < w - 2) ? contentBuffer[i, j] : " ";
                    }
                    else
                    {
                        tmp[i, j] = " ";
                    }                    
                }
            }

            // overwrite old buffer with the new one
            contentBuffer = tmp;

            // regenerate box buffer
            GenerateBoxBuffer();

            // redraw
            Refresh();
        }

        public void Refresh() 
        {

            // Store current cursor position and current colors
            CursorPosition cp = CursorPosition.GetCurrent();
            ConsoleColors cc = ConsoleColors.GetCurrent();

            // Apply box colors to console
            CurrentColors.ApplyToConsole();

            // Nothing will be draw for coordinates outside console limits
            for (int i = 0; i < height; i++)
            {
                if ((top + i) < Console.BufferHeight && (left < Console.BufferWidth))
                {
                    Console.SetCursorPosition(left, top + i);
                    string s = string.Empty;
                    for (int j = 0; j < width; j++)
                    {
                        if ((left + j) < Console.BufferWidth) s += boxBuffer[i, j];
                    }
                    if (!string.IsNullOrEmpty(s))
                    {
                        // if last line in buffer and overflow, increase buffer size before writing 
                        // to avoid generation of a new line in buffer, then reposition the cursor to top left corner
                        // and set buffer size back to original size
                        if ((top + i) == Console.BufferHeight - 1)
                        {
                            Console.SetBufferSize(Console.BufferWidth, Console.BufferHeight + 1);
                            Console.Write(s);
                            Console.SetCursorPosition(0,0);
                            Console.SetBufferSize(Console.BufferWidth, Console.BufferHeight - 1);                            
                        }
                        else
                            Console.Write(s);
                    }
                }
            }

            // Reset cursor at initial position and initial console colors
            cp.ApplyToConsole();
            cc.ApplyToConsole();
        }

        private void GenerateBoxBuffer()
        {
            // Generate borders and title, and fill with content
            boxBuffer = new string[height, width];

            // Borders (ASCII codes)
            // ---------------------
            // Double       Single
            // 186 : ║      179 : │
            // 205 : ═      196 : ─
            // 201 : ╔      218 : ┌
            // 200 : ╚      192 : └
            // 187 : ╗      191 : ┐
            // 188 : ╝      217 : ┘

            // Define borders chars
            string first, last, inside;
            for (int i = 0; i < height; i++)
            {
                if ((i == 0) || (i == height - 1))
                {
                    if (i == 0)
                    {
                        first = borderStyle == ConsoleBoxBorderStyle.Double ? "╔" : "┌";
                        last = borderStyle == ConsoleBoxBorderStyle.Double ? "╗" : "┐";
                        inside = borderStyle == ConsoleBoxBorderStyle.Double ? "═" : "─";
                    }
                    else
                    {
                        first = borderStyle == ConsoleBoxBorderStyle.Double ? "╚" : "└";
                        last = borderStyle == ConsoleBoxBorderStyle.Double ? "╝" : "┘";
                        inside = borderStyle == ConsoleBoxBorderStyle.Double ? "═" : "─";
                    }

                    string tmptitle = string.Empty;
                    if (width - 8 > 0)
                    {
                        tmptitle = string.IsNullOrEmpty(title) ? "" : string.Format(" {0} ", title.Length > width - 8 ? title.Substring(0, width - 8) : title);
                        if (tmptitle == "  ") tmptitle = string.Empty;
                    }

                    boxBuffer[i, 0] = first;
                    for (int j = 1; j < width - 1; j++)
                    {
                        if (j > 1 && j < width - 4 && i == 0)
                        {
                            // Title zone
                            boxBuffer[i, j] = tmptitle.Length > j - 2 ? tmptitle[j - 2].ToString() : inside;
                        }
                        else
                        {
                            boxBuffer[i, j] = inside;
                        }
                    }
                    boxBuffer[i, width - 1] = last;
                }
                else
                {
                    first = last = borderStyle == ConsoleBoxBorderStyle.Double ? "║" : "│";

                    // Fill with buffer content
                    boxBuffer[i, 0] = first;
                    for (int j = 1; j < width - 1; j++)
                    {
                        boxBuffer[i, j] = contentBuffer[i - 1, j - 1];
                    }
                    boxBuffer[i, width - 1] = last;
                }
            }            
        }
        #endregion
    }
}
